/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, ts } from "arkanalyzer";
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { AstMatcher, MatcherCallback, MatcherTypes } from '../../Index';
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";

const gMetaData: BaseMetaData = {
    severity: 1,
    ruleDocPath: "docs/no-dupe-class-members.md",
    description: "Disallow duplicate class members.",
};

// 定义一个接口，用于存储问题的行列信息
interface LocationInfo {
    fileName: string;
    line: number;
    character: number;
    description: string;
}

interface ClassMember {
    memberName: string;
    memberKind: string;
    isStatic: boolean;
}

export class NoDupeClassMembersCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private buildMatcher: AstMatcher = {
        matcherType: MatcherTypes.AST,
        match(node: ts.Node): boolean {
            return ts.isClassDeclaration(node);
        }
    };

    public registerMatchers(): MatcherCallback[] {
      const matchBuildCb: MatcherCallback = {
          matcher: this.buildMatcher,
          callback: this.check
      };
      return [matchBuildCb];
    };

    public check = (node: ts.ClassDeclaration, target: ArkFile) => {
        const sourceFile = node.getSourceFile();
        const memberMap = new Map<string, ClassMember[]>();

        node.members.forEach(member => {
            const memberInfo = this.extractMemberInfo(member);
            if (!memberInfo) {
                return;
            }

            const existingMembers = memberMap.get(memberInfo.memberName) || [];

            // 判断是否属于重复定义（Duplicate Class Member）
            // 判定规则：
            // 1. 名称相同且静态属性（isStatic）一致的成员，视为潜在重复。
            // 2. 以下情况除外：
            //    - 成员之一为重载函数（overload），允许共存。
            //    - 一对 getter / setter 互为访问器配对。
            // 若满足以上条件，则认定为重复成员。

            if (memberInfo.memberKind === 'overload') {
                existingMembers.push(memberInfo);
                memberMap.set(memberInfo.memberName, existingMembers);
                return;
            }

            const shouldReportDuplicate = existingMembers.some((m: ClassMember) => {
                if (m.isStatic !== memberInfo.isStatic) {
                    return false;
                }
                if (m.memberKind === 'overload') {
                    return false;
                }
                const isGetterSetterMatch = (m.memberKind === 'getter' && memberInfo.memberKind === 'setter') ||
                    (m.memberKind === 'setter' && memberInfo.memberKind === 'getter');
                if (isGetterSetterMatch) {
                    return false;
                }
                return true;
            });

            if (shouldReportDuplicate) {
                const { line, character } = sourceFile.getLineAndCharacterOfPosition(member.getStart());
                this.addIssueReportNode({
                    fileName: sourceFile.fileName,
                    line: line + 1,
                    character: character + 1,
                    description: `Duplicate name '${memberInfo.memberName}'.`
                }, target.getFilePath());
            }
            existingMembers.push(memberInfo);
            memberMap.set(memberInfo.memberName, existingMembers);
        })
    }

    private extractMemberInfo(member: ts.ClassElement): ClassMember | null {
        let memberName: string | undefined;
        let memberKind = '';
        const sourceFile = member.getSourceFile();
        const isStatic = !!member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
        
        if (ts.isMethodDeclaration(member)) {
            memberName = this.normalizeMemberName(member.name.getText(sourceFile), member);
            memberKind = member.body ? 'method' : 'overload'; //重载函数
        } else if (ts.isPropertyDeclaration(member)) {
            memberName = this.normalizeMemberName(member.name.getText(sourceFile), member);
            memberKind = 'property';
        } else if (ts.isGetAccessorDeclaration(member)) {
            memberName = this.normalizeMemberName(member.name.getText(sourceFile), member);
            memberKind = 'getter';
        } else if (ts.isSetAccessorDeclaration(member)) {
            memberName = this.normalizeMemberName(member.name.getText(sourceFile), member);
            memberKind = 'setter';
        }

        if (!memberName) {
            return null;
        }
        return { memberName, memberKind, isStatic };
    }

    // 标准化成员名称
    private normalizeMemberName(memberName: string, member: ts.ClassElement): string {
        if (member.name) {
            if (ts.isNumericLiteral(member.name)) {
                // 将数值名称解析为数值后再转换为字符串
                return parseFloat(member.name.getText()).toString();
            } else if (ts.isIdentifier(member.name) || ts.isStringLiteral(member.name)) {
                return member.name.text;
            }
        }
        return memberName.replace(/'/g, '');
    }

    private addIssueReportNode(info: LocationInfo, filePath: string): void {
        const severity = this.rule.alert ?? this.metaData.severity;
        if (info.description) {
            this.metaData.description = info.description;
        }
        let defect = new Defects(info.line, info.character, info.character, this.metaData.description, severity,
            this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }
}